﻿using System;
using Server.Items;
using System.Collections;
using System.Collections.Generic;
using Server.Commands;
using Server.Mobiles;
using Server.Targeting;
using Server.Network;
using Server.Spells;

namespace Server.Engines.CastSystem
{
    public static class CastSystem
    {
        private static Dictionary<Mobile, Dictionary<Cast, DateTime>> TabellaBlessOrCurseAttivi;

        public static void UpdateTabella(Mobile m)
        {
            if (TabellaBlessOrCurseAttivi == null)
            {
                return;
            }

            Dictionary<Cast, DateTime> Attivi;
            if (!TabellaBlessOrCurseAttivi.TryGetValue(m, out Attivi))
            {
                return;
            }

            bool purgeFinito = false;

            while (!purgeFinito)
            {
                // Eliminiamo tutti i bless o curse scaduti.
                foreach (KeyValuePair<Cast, DateTime> blessOrCurse in Attivi)
                {
                    if (DateTime.Now > blessOrCurse.Value)
                    {
                        Attivi.Remove(blessOrCurse.Key);
                        break;
                    }
                }
                purgeFinito = true;
            }

        }

        public static bool CurseOrBlessLookUp(Mobile m, Type CastType)
        {
            if (TabellaBlessOrCurseAttivi == null)
            {
                return false;
            }

            Dictionary<Cast, DateTime> Attivi;
            if (!TabellaBlessOrCurseAttivi.TryGetValue(m, out Attivi))
            {
                return false;
            }

            UpdateTabella(m);

            foreach (KeyValuePair<Cast, DateTime> blessOrCurse in Attivi)
            {
                if (blessOrCurse.Key.GetType() == CastType)
                    return true;
            }

            return false;
        }

        // Ritorna false se il PG non aveva il bless attivo, true se ce lo aveva ed l'effetto e' stato rinnovato 
        public static bool RegisterCurseOrBless(Mobile m, Cast cast, TimeSpan delay, bool Rinnovabile)
        {
            if (TabellaBlessOrCurseAttivi == null)
                TabellaBlessOrCurseAttivi = new Dictionary<Mobile, Dictionary<Cast, DateTime>>();

            Dictionary<Cast, DateTime> Attivi;

            if (!TabellaBlessOrCurseAttivi.TryGetValue(m, out Attivi))
            {
                Attivi = new Dictionary<Cast, DateTime>();
                TabellaBlessOrCurseAttivi.Add(m, Attivi);
            }

            UpdateTabella(m);

            bool potereRinnovato = false;

            // Controlliamo che il target sia gia sotto l'effetto di questo bless o curse.
            foreach (KeyValuePair<Cast, DateTime> blessOrCurse in Attivi)
            {
                if (blessOrCurse.Key.GetType() == cast.GetType())
                {
                    if (!Rinnovabile)
                        return false;

                    Attivi.Remove(blessOrCurse.Key);
                    potereRinnovato = true;
                }
            }

            // Aggiungiamo il curse o il bless
            Attivi.Add(cast, DateTime.Now + delay);
           
            return potereRinnovato;
        }

        public static void LanciaCast(Cast cast)
        {
            if (cast.RichiedeWarMode && !cast.Caster.Warmode)
            {
                cast.Caster.SendMessage("Devi essere in war mode");
                cast.Clear();
                return;
            }

            if (cast.Caster.Spell != null)
            {
                ((CastMagia)(cast.Caster.Spell)).DoFizzle();
                ((CastMagia)(cast.Caster.Spell)).Clear();
                cast.Clear();
                return;
            }

            // Il caster ha i prequisiti per lanciare questo cast?
            if (!cast.checkRequisiti())
            {
                cast.Clear();
                return;
            }

            // Il cast ha una fase di recitazione (pre-cast)
            if (cast.TempoDiRecitazione > 0)
            {
                // Non puo' pagare il costo di recitazione
                if (!cast.OnInizioRecitazione())
                {
                    cast.Clear();
                    return;
                }

                if (cast is ISpell)
                {
                    cast.Caster.Spell = (ISpell)cast;
                }

                cast.Recita();
            }
            else
            {
                if (cast.RichiedeTarget)
                {
                    CastTarget target = new CastTarget(cast);
                    cast.Caster.Target = target;
                    cast.Target = target;
                }
                else
                {
                    if (!cast.OnBeforeCast())
                        return;
                    
                    if (cast.FaPerdereIlColpo)
                        cast.Caster.PerdiIlColpo();

                    // Cast ad area
                    if (cast.CastAdArea)
                    {
                        IPooledEnumerable mobiles = cast.Caster.Map.GetMobilesInRange( cast.Caster.Location, cast.MaxRange );

                        foreach (Mobile m in mobiles)
                        {
                            // Gli NPG quando castano ad area non colpiscono gli altri NPG
                            if (cast.Caster is BaseCreature && m is BaseCreature)
                                continue;

                            // PG cast ad area su un altro PG
                            if (cast.Caster is PlayerMobile && m is PlayerMobile)
                            {
                                PlayerMobile playerCaster = ((PlayerMobile)cast.Caster);
                                PlayerMobile playerTarget = ((PlayerMobile)m);
                                
                                // se il cast e' di tipo dannoso, non colpisce i membri del proprio gruppo.
                                if (cast.Tipo == CastType.Dannoso && playerCaster.Gruppo != null && playerCaster.Gruppo.Appartenente(playerTarget))
                                {
                                    continue;
                                }

                                // se il cast e' di tipo benefico, colpisce SOLO i membri del proprio gruppo.
                                if (cast.Tipo == CastType.Benefico && (playerCaster.Gruppo == null || !playerCaster.Gruppo.Appartenente(playerTarget)))
                                {
                                    continue;
                                }
                            }

                            // PG cast ad area su un NPG
                            if (cast.Caster is PlayerMobile && m is BaseCreature)
                            {
                                PlayerMobile playerCaster = ((PlayerMobile)cast.Caster);
                                BaseCreature targetCreature = ((BaseCreature)m);

                                // Il caster e' owner della creatura target: non subisce cast dannosi
                                if (targetCreature.Owners.Contains(playerCaster) && cast.Tipo == CastType.Dannoso)
                                {
                                    continue;
                                }

                                // Il caster non e' owner della creatura target: non subisce cast benefici
                                if (!targetCreature.Owners.Contains(playerCaster) && cast.Tipo == CastType.Benefico)
                                {
                                    continue;
                                }
                            }

                            cast.OnCastTarget(m);
                        }

                        IPooledEnumerable items = cast.Caster.Map.GetItemsInRange(cast.Caster.Location, cast.MaxRange);

                        foreach (Item i in items)
                        {
                            cast.OnCastTarget(i);
                        }
                    }
                    // Cast semplice
                    else
                    {
                        cast.OnCast();
                    }

                    cast.OnAfterCast();
                    cast.Clear();
                }
            }
        }
    }

    public enum CastType
    {
        Benefico,
        Dannoso,
        Indefinito
    }

    public enum DamageType
    {
        Fisico,
        Energia,
        Elettricita,
        Freddo,
        Fuoco,
        Acido,
        Veleno,
        Fisso
    }

    public class Cast
    {
        private CastTimer m_Timer;
        private CastTarget m_Target;
        private Mobile m_Caster;
        protected int m_StepCounter;
        private bool m_Expired;
        private List<NomeClasse> m_ClassiAbilitate;

        public virtual double DefTempoDiRecitazione { get { return 0.0; } }
        public virtual double DefTempoDiColpoInCanna { get { return 0.0; } }
        public virtual bool DefFaPerdereIlColpo { get { return false; } }
        public virtual bool DefRichiedeTarget { get { return false; } }
        public virtual int DefMaxRange { get { return 0; } }
        public virtual bool DefCastAdArea { get { return false; } }
        public virtual CastType DefTipo { get { return CastType.Indefinito; } }
        public virtual DamageType DefTipoDanno { get { return DamageType.Fisso; } }
        public virtual bool DefRichiedeWarMode { get { return false; } }  

        public Cast(Mobile caster)
        {
            m_Caster = caster;
            m_Expired = false;
        }

        public CastTarget Target
        {
            set
            {
                m_Target = value;
            }
            get
            {
                return m_Target;
            }
        }

        public bool Expired
        {
            get
            {
                return m_Expired;
            }
            set
            {
                m_Expired = value;
            }
        }

        // Ritorna true se il caster ha i requisiti per lanciare questo cast
        public virtual bool checkRequisiti()
        {
            return true;
        }

        // Paga il costo di recitazione. Se non puo' pagare il costo ritorna false.
        public virtual bool OnInizioRecitazione()
        {
            return true;
        }

        public virtual void Recita()
        {
            m_StepCounter = 0;

            if (m_Caster is PlayerMobile)
                ((PlayerMobile)m_Caster).CastAttivo = this;

            Timer = new CastTimer(m_Caster, this, TimeSpan.FromSeconds(TempoDiRecitazione), CastTimerType.Recitazione);
            Timer.Start();
        }

        public virtual bool OnFineRecitazione()
        {
            m_Caster.Spell = null;

            return true;
        }

        public virtual bool OnBeforeCast()
        {
            return true;
        }

        public virtual bool OnBeforeCast(Mobile target)
        {
            return true;
        }

        public virtual bool OnBeforeCast(Item target)
        {
            return true;
        }

        public virtual void OnCast()
        {
        }

        public virtual void OnCastTarget(Item item)
        {
        }

        public virtual void OnCastTarget(Mobile mobile)
        {
        }

        public virtual void OnAfterCast()
        {
        }

        public virtual void OnEffectExpired()
        {
        }

        public virtual void Clear()
        {
            m_Caster.Spell = null;

            if (Timer != null)
                Timer.Stop();

            if (Target != null)
                Target.Cancel(m_Caster, TargetCancelType.Timeout);

            // TODO: implementare il cast attivo anche sulle creature
            if (m_Caster != null && m_Caster is PlayerMobile)
                ((PlayerMobile)m_Caster).CastAttivo = null;
        }

        public virtual void CasterMovement()
        {
            m_StepCounter++;
        }

        #region setters_and_getters

        // Setters and getters
        public virtual double TempoDiRecitazione { get { return DefTempoDiRecitazione; } }
        public virtual double TempoDiColpoInCanna { get { return DefTempoDiColpoInCanna; } }
        public virtual bool FaPerdereIlColpo { get { return DefFaPerdereIlColpo; } }
        public virtual bool RichiedeTarget { get { return DefRichiedeTarget; } }
        public virtual int MaxRange { get { return DefMaxRange; } }
        public virtual bool CastAdArea { get { return DefCastAdArea; } }
        public virtual Mobile Caster { get { return m_Caster; } }
        public virtual CastType Tipo { get { return DefTipo; } }
        public virtual DamageType TipoDanno { get { return DefTipoDanno; } }
        public virtual bool RichiedeWarMode { get { return DefRichiedeWarMode;  } }
        public virtual CastTimer Timer
        {
            get
            {
                return m_Timer;
            }

            set
            {
                m_Timer = value;
            }
        }

        #endregion
    }

    public enum CastTimerType
    {
        Recitazione,
        WaitingForTarget,
        Done
    }

    public class CastTimer : Timer
    {
        private Cast m_Cast;
        private Mobile m_Caster;
        private CastTimerType m_Type;

        public CastTimer(Mobile from, Cast cast, TimeSpan castDelay, CastTimerType type)
            : base(castDelay)
        {
            m_Cast = cast;
            m_Caster = from;
            m_Type = type;

            Priority = TimerPriority.TwentyFiveMS;
        }

        // Quando scade il timer.
        protected override void OnTick()
        {
            switch (m_Type)
            {
                case CastTimerType.Recitazione:
                    {
                        if (!m_Cast.OnFineRecitazione())
                        {
                            m_Cast.Clear();
                            return;
                        }

                        if (m_Cast.RichiedeTarget)
                        {
                            CastTarget target = new CastTarget(m_Cast);
                            m_Cast.Caster.Target = target;
                            m_Cast.Target = target;

                            if (m_Cast.TempoDiColpoInCanna > 0)
                            {
                                CastTimer timer = new CastTimer(m_Caster, m_Cast, TimeSpan.FromSeconds(m_Cast.TempoDiColpoInCanna), CastTimerType.WaitingForTarget);
                                timer.Start();
                            }
                        }
                        else
                        {
                            if (!m_Cast.OnBeforeCast())
                                return;

                            if (m_Cast.FaPerdereIlColpo)
                                m_Cast.Caster.PerdiIlColpo();

                            // Cast ad area
                            if (m_Cast.CastAdArea)
                            {
                                IPooledEnumerable mobiles = m_Cast.Caster.Map.GetMobilesInRange(m_Cast.Caster.Location, m_Cast.MaxRange);

                                foreach (Mobile m in mobiles)
                                {
                                    m_Cast.OnCastTarget(m);
                                }
                            }
                            // Cast semplice
                            else
                            {
                                m_Cast.OnCast();
                            }

                            m_Cast.OnAfterCast();
                            m_Cast.Clear();
                        }
                        break;
                    }
                case CastTimerType.WaitingForTarget:
                    {
                        if (m_Caster is PlayerMobile && m_Caster.Spell == m_Cast)
                            ((PlayerMobile)m_Caster).SendMessage("Il cast svanisce");

                        m_Cast.Expired = true;

                        m_Cast.Clear();
                        break;
                    }
                default:
                    {
                        break;
                    }
            }
        }
    }

    public class CastTarget : Target
    {
        private Cast m_Cast;

        public CastTarget(Cast cast)
            : base(cast.MaxRange, false, TargetFlags.None)
        {
            m_Cast = cast;
        }

        protected override void OnTarget(Mobile from, object targeted)
        {
            if (m_Cast.Expired)
            {
                m_Cast.Clear();
                return;
            }

            if (targeted is Mobile)
            {
                if (!m_Cast.OnBeforeCast((Mobile)targeted))
                    return;

                if (m_Cast.FaPerdereIlColpo)
                    m_Cast.Caster.PerdiIlColpo();

                m_Cast.Caster.aggiornaDirezioneDuranteCaricaScarica((Mobile)targeted, false);

                m_Cast.OnCastTarget((Mobile)targeted);
            }
            else if (targeted is Item)
            {
                if (!m_Cast.OnBeforeCast((Item)targeted))
                    return;

                if (m_Cast.FaPerdereIlColpo)
                    m_Cast.Caster.PerdiIlColpo();

                m_Cast.OnCastTarget((Item)targeted);
            }

            m_Cast.OnAfterCast();
            m_Cast.Clear();
        }
    }

        public class CastMagia : Cast, ISpell
        {
            private SpellInfo m_SpellInfo;

            public virtual bool DefBloccaMovimenti { get { return false; } }
            public virtual SkillName DefSkill { get { return SkillName.Magery;  } }
            public virtual int DefMana { get { return 0; } }
            public virtual double DefPassiAlSecondo { get { return 2.0; } }
            public virtual int Livello { get { return 1; } }

            // Overrides
            public override int DefMaxRange { get { return 10; } }
            public override double DefTempoDiColpoInCanna { get { return 7.0; } }
            public override bool DefFaPerdereIlColpo { get { return true; } }
            public override bool DefRichiedeWarMode { get { return true; } }
            
            public CastMagia(Mobile caster, SpellInfo info) : base(caster)
            {
                m_SpellInfo = info;
            }

            // ISpell interface
            public virtual bool IsCasting { get { return (Timer != null); } }
            
            public virtual void OnCasterHurt()
            {
                // TODO: Probabilita' di flizzare?
            }
            
            public virtual void OnCasterKilled()
            {
                Clear();
            }

            public virtual void OnConnectionChanged()
            {
                Clear();
            }

            public virtual bool OnCasterMoving(Direction d)
            {
                if (IsCasting)
                    m_StepCounter++;

                if (IsCasting && BloccaMovimenti)
                {
                    Caster.SendMessage("Non puoi muoverti durante la recitazione di questa magia"); 
                    return false;
                }

                return true;
            }

            public virtual bool OnCasterEquiping(Item item)
            {
                if (IsCasting)
                {
                    // Che facciamo se equippiamo durante la recitazione?
                }

                return true;
            }

            public virtual bool OnCasterUsingObject(object o)
            {
                if (IsCasting)
                {
                    // Che facciamo se equippiamo durante la recitazione?
                }
                return true;
            }

            public virtual bool OnCastInTown(Region r)
            {
                return true;
            }

            public virtual bool ConsumaReagenti()
            {
                // I mostri non consumano reagenti.
                if (! (Caster is PlayerMobile))
                    return true;

                // I GM non consumano reagenti.
                if (Caster.AccessLevel > AccessLevel.Player)
                    return true;

                Container pack = Caster.Backpack;

                if (pack == null)
                    return false;

                if (pack.ConsumeTotal(m_SpellInfo.Reagents, m_SpellInfo.Amounts) == -1)
                    return true;

                return false;
            }

            public override void Recita()
            {
                base.Recita();
            }

            public virtual bool BloccaMovimenti { get { return DefBloccaMovimenti; } }
            public virtual SkillName Skill { get { return DefSkill;  } }
            public virtual int Mana { get { return DefMana; } }
            public virtual double PassiAlSecondo { get { return DefPassiAlSecondo; } }

            public override bool OnInizioRecitazione()
            {
                if (Caster.Mana < Mana)
                {
                    Caster.SendMessage("Non hai abbastanza mana");
                    return false;
                }

                if (!ConsumaReagenti())
                {
                    Caster.SendMessage("Non hai i reagenti adatti");
                    return false;
                }

                // Consuma il mana richiesto
                Caster.Mana = Caster.Mana - Mana;

                // Mantra sopra la testa del caster (in runico)
                if (m_SpellInfo.Mantra != null && m_SpellInfo.Mantra.Length > 0 && Caster is PlayerMobile)
                    // Mettere il colore giusto per il runico.
                    Caster.PublicOverheadMessageInRunico(MessageType.Spell, Caster.SpeechHue, m_SpellInfo.Mantra, false);

                return true;
            }

            public override bool OnFineRecitazione()
            {
                if (m_StepCounter > PassiAlSecondo * TempoDiRecitazione)
                {
                    Caster.SendMessage("Ti sei mosso troppo");
                    DoFizzle();

                    return false;
                }

                return base.OnFineRecitazione();
            }

            public virtual void DoFizzle()
            {    
                Caster.FixedParticles(0x3735, 1, 30, 9503, EffectLayer.Waist);
                    
                //Caster.FixedEffect(0x3735, 6, 30);

                Caster.PlaySound(0x5C);
            }
        }

        public static class CastHelper
        {
            public static void ApplicaDannoMagico(Mobile from, Mobile to, int danno, DamageType tipoDanno, bool usaMageryAndResSpell)
            {
                if (usaMageryAndResSpell)
                {
                    // Bonus da magery: un danno per ogni 2.5 di skill
                    danno += (int)Math.Round(from.Skills[SkillName.Magery].Value / 2.5);

                    // Riduzione danno da resSpell: un danno ogni 2.5 di skill
                    danno -= (int)Math.Round(to.Skills[SkillName.MagicResist].Value / 2.5);
                }

                double resistenza = 0.0;

                switch(tipoDanno)
                {
                    case DamageType.Fisico:
                    {
                        resistenza = to.GetResistance(ResistanceType.Physical);
                        break;
                    }
                    case DamageType.Energia:
                    {
                        resistenza = to.GetResistance(ResistanceType.Energy);
                        break;
                    }
                    case DamageType.Elettricita:
                    {
                        resistenza = to.GetResistance(ResistanceType.Elettricita);
                        break;
                    }
                    case DamageType.Acido:
                    {
                        resistenza = to.GetResistance(ResistanceType.Acido);
                        break;
                    }
                    case DamageType.Fisso:
                    {
                        resistenza = 0.0;
                        break;
                    }
                    case DamageType.Freddo:
                    {
                        resistenza = to.GetResistance(ResistanceType.Cold);
                        break;
                    }
                    case DamageType.Fuoco:
                    {
                        resistenza = to.GetResistance(ResistanceType.Fire);
                        break;
                    }
                    case DamageType.Veleno:
                    {
                        resistenza = to.GetResistance(ResistanceType.Poison);
                        break;
                    }
                    default:
                    {
                        resistenza = 0.0;
                        break;
                    }
                }

                // Applica resistenza
                danno = (int)Math.Round(danno * (100.0 - resistenza) / 100.0);

                if (danno > 0)
                {
                    to.Damage(danno, from);
                }
            }

            // Da sito: Il danno aggiunto per livello viene calcolato fino a 5 livelli in più del livello a cui si è in grado di castare lo spell.
            public static int BonusDannoDaLivello(CastMagia cast)
            {
                if (cast == null)
                    return 0;

                if (cast.Caster == null)
                    return 0;

                if (cast.Caster.Livello <= cast.Livello + 6)
                    return cast.Caster.Livello;
                else
                    return cast.Livello + 6;
            }
        }
}